package com.crois.barrier.web.loadbalance.strategy;

import com.crois.barrier.common.dto.ApiRouteInfo;
import com.crois.barrier.common.dto.BarrierClientInstance;
import com.crois.barrier.common.dto.IpAndWeight;
import com.crois.barrier.common.enums.UpstreamStrategyEnum;
import com.crois.barrier.common.exception.BarrierException;
import com.crois.barrier.common.exception.BarrierExceptionEnum;
import com.crois.barrier.web.loadbalance.rule.LoadBalanceRule;
import com.crois.barrier.web.loadbalance.rule.WeightRoundRobinLoadBalanceRule;
import com.crois.barrier.web.spring.SpringBeanUtils;
import com.crois.barrier.web.support.ServerWebExchangeUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static com.crois.barrier.web.support.ParamMappingUtils.queryMapToString;

/**
 * @author Hou Ze Yu
 * @description ip list 转发 策略
 * @date 2020/7/20
 */
public class IpListDivideStrategy implements DivideStrategy{

    Logger LOGGER = LoggerFactory.getLogger(IpListDivideStrategy.class);

    @Override
    public void divide(ApiRouteInfo apiRouteInfo, ServerWebExchange webExchange) {
        LOGGER.info("[begin invoke IpListDivide strategy]");

        if (apiRouteInfo==null || !apiRouteInfo.getUpstreamStrategy().equals(UpstreamStrategyEnum.IP_LIST_ROUTE.getName())){
          throw new BarrierException(BarrierExceptionEnum.BARRIER_GATEWAY_FAIL);
        }
        List<IpAndWeight> ipList = apiRouteInfo.getIpList();

        if (CollectionUtils.isEmpty(ipList)){
            LOGGER.info("[IpListDivide strategy],not have available ip list");
        }
        LoadBalanceRule wightRoundRobinLoadBalanceRule = SpringBeanUtils.getInstance().getBean(WeightRoundRobinLoadBalanceRule.class);

        List<BarrierClientInstance> barrierClientInstanceList = buildInstanceList(apiRouteInfo);

        BarrierClientInstance chooseClientInstance = wightRoundRobinLoadBalanceRule.choose(barrierClientInstanceList,webExchange);
        String requestRrl =  buildRequestUrl(chooseClientInstance,webExchange,apiRouteInfo.getProtocol());
        if (StringUtils.isNotEmpty(requestRrl)){
            webExchange.getAttributes().put(ServerWebExchangeUtils.BARRIER_REQUEST_URL_ATTR,requestRrl);
        }
        LOGGER.info("[end invoke IpListDivide strategy]");
    }

    /**
     * 构建请求url
     * @param chooseClientInstance
     * @param webExchange
     * @param protocol
     * @return
     */
    private String buildRequestUrl(BarrierClientInstance chooseClientInstance, ServerWebExchange webExchange, String protocol) {
        String requestPath = webExchange.getRequest().getPath().toString();
        String ip = chooseClientInstance.getIp();
        int port = chooseClientInstance.getPort();
        String requestProtocol = Optional.ofNullable(protocol).orElse("http");
        String url = requestProtocol+"://"+ip+":"+port+requestPath;
        MultiValueMap<String, String> queryParams = webExchange.getRequest().getQueryParams();
        if (queryParams.size()!=0){
            String afterQueryString = queryMapToString(queryParams);
            url= url+"?"+afterQueryString;
        }
        return url;

    }

    /***
     * 构建 实例 列表
     * @param apiRouteInfo
     * @return
     */
    private List<BarrierClientInstance> buildInstanceList(ApiRouteInfo apiRouteInfo) {
        List<BarrierClientInstance> doorClientInstanceList =new ArrayList<>(apiRouteInfo.getIpList().size());
        if (CollectionUtils.isNotEmpty(apiRouteInfo.getIpList())){
            for (IpAndWeight ipAndWeight : apiRouteInfo.getIpList()) {
                URI uri = URI.create(apiRouteInfo.getProtocol()+"://"+ipAndWeight.getIp());
                int port = uri.getPort();
                String host = uri.getHost();
                int weight = ipAndWeight.getWeight();
                BarrierClientInstance doorClientInstance=new BarrierClientInstance();
                doorClientInstance.setWeight(weight);
                doorClientInstance.setPort(port);
                doorClientInstance.setIp(host);
                doorClientInstance.setServiceName(apiRouteInfo.getApiRouteName());
                doorClientInstanceList.add(doorClientInstance);
            }
        }
        return doorClientInstanceList;
    }
}
